package com.cn.sl.server.api.system.file.service.impl

import com.cn.sl.server.api.system.file.config.LocalFileUploadConfig
import com.cn.sl.server.api.system.file.dao.rdb.SysFileDao
import com.cn.sl.server.api.system.file.exception.DeleteFileException
import com.cn.sl.server.api.system.file.exception.DownloadFileException
import com.cn.sl.server.api.system.file.model.dto.SysFileDto
import com.cn.sl.server.api.system.file.model.dto.UploadResultDto
import com.cn.sl.server.api.system.file.model.entity.QSysFileEntity
import com.cn.sl.server.api.system.file.model.entity.SysFileEntity
import com.cn.sl.server.api.system.file.model.enumerate.FileType
import com.cn.sl.server.api.system.file.service.FileUploadService
import com.cn.sl.server.api.system.file.service.SysFileService
import com.cn.sl.server.common.annotation.transaction.Commit
import com.cn.sl.server.common.annotation.transaction.ReadOnly
import com.querydsl.core.types.dsl.Expressions
import com.querydsl.jpa.impl.JPAQueryFactory
import jakarta.servlet.http.HttpServletResponse
import org.dromara.hutool.core.io.file.PathUtil
import org.dromara.hutool.core.text.CharSequenceUtil
import org.dromara.hutool.core.util.RandomUtil
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.web.multipart.MultipartFile
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream

/**
 * @author 阮雪峰
 */
@Service
class SysFileServiceImpl(
    val fileUploadService: FileUploadService,
    val sysFileDao: SysFileDao,
    val localFileUploadConfig: LocalFileUploadConfig,
    val jpaQueryFactory: JPAQueryFactory,
    val response: HttpServletResponse
) : SysFileService {

    val logger: Logger = LoggerFactory.getLogger(SysFileServiceImpl::class.java)
    val entity: QSysFileEntity = QSysFileEntity.sysFileEntity

    @Commit
    override fun save(file: MultipartFile, type: FileType): SysFileDto {
        val fileEntity = SysFileEntity()
        sysFileDao.save(fileEntity)
        //获取文件后缀名
        val suffix = if (CharSequenceUtil.isNotEmpty(file.originalFilename)) CharSequenceUtil.subAfter(
            file.originalFilename,
            ".",
            true
        ) else DEFAULT_FILE_SUFFIX

        val key: String = createKey(fileEntity.id, type, suffix)
        val url: String = fileUploadService.upload(file, key)

        fileEntity.apply {
            this.fileName = file.originalFilename ?: RandomUtil.randomString(6)
            this.type = type
            this.dir = key
            this.url = url
        }

        return SysFileDto(fileEntity)
    }

    @Commit
    override fun save(
        filename: String,
        type: FileType,
        write: (fileOutputStream: FileOutputStream) -> Unit
    ): UploadResultDto {
        val fileEntity = SysFileEntity()
        sysFileDao.save(fileEntity)
        //获取文件后缀名
        val suffix = if (CharSequenceUtil.isNotEmpty(filename)) CharSequenceUtil.subAfter(
            filename,
            ".",
            true
        ) else DEFAULT_FILE_SUFFIX

        val key = createKey(fileEntity.id, type, suffix)

        // 将流写入文件
        val file = File(localFileUploadConfig.filePath + key)
        if (!file.parentFile.exists() && file.parentFile.mkdirs()) {
            logger.info("创建文件夹成功（{}）", localFileUploadConfig.filePath + key)
        }
        val fileOutputStream = FileOutputStream(file)

        write(fileOutputStream)

        val url: String = key.replace(File.separator, "/") + "?t=" + System.currentTimeMillis()

        fileEntity.apply {
            this.fileName = filename
            this.type = type
            this.dir = key
            this.url = url
        }

        return UploadResultDto(fileEntity.id, "${localFileUploadConfig.fullUrlPrefix}$url")
    }

    @Commit
    override fun deleteTemp() {
        val days = Expressions.numberTemplate(
            Int::class.java,
            "cast(to_number(to_char(age({0}), 'dd'),'999') as integer)",
            entity.createdTime
        )
        val sysFileEntities = jpaQueryFactory.selectFrom(entity)
            .where(days.goe(1))
            .fetch()
        removeAll(sysFileEntities)
    }

    @ReadOnly
    override fun download(id: Long) {
        val entity: SysFileEntity = sysFileDao.findById(id)
        try {
            FileInputStream("${localFileUploadConfig.filePath}${File.separator}${entity.dir}").use {
                val body: ByteArray = it.readAllBytes()
                response.addHeader(
                    "Content-Disposition",
                    "attachment;filename=${URLEncoder.encode(entity.fileName, StandardCharsets.UTF_8)}"
                )
                response.setContentLength(body.size)
                //获取Servlet的输出流ServletOutputStream
                response.outputStream.use { sos -> sos.write(body) }
            }
        } catch (e: IOException) {
            logger.error("下载文件${entity.fileName}（${entity.id}）发生异常", e)
            throw DownloadFileException("下载文件${entity.fileName}发生出错", e)
        }
    }

    @ReadOnly
    override fun downloadAll(ids: List<Long>) {
        if (ids.isEmpty()) {

            return
        }
        val entities: List<SysFileEntity> = sysFileDao.findAllById(ids)
        response.addHeader(
            "Content-Disposition",
            "attachment;filename=${URLEncoder.encode("ALL-FILES.zip", StandardCharsets.UTF_8)}"
        )
        response.outputStream.use { out ->
            ZipOutputStream(out).use { zipOutputStream ->
                for (entity in entities) {
                    val entry = ZipEntry(entity.fileName)
                    zipOutputStream.putNextEntry(entry)
                    FileInputStream("${localFileUploadConfig.filePath}${File.separator}${entity.dir}").use { stream ->
                        zipOutputStream.write(stream.readAllBytes())
                    }
                }
            }
        }
    }

    @ReadOnly
    override fun downloadTemplate(templateName: String) {
        try {
            FileInputStream("${localFileUploadConfig.exportTemplatePath}${File.separator}${templateName}").use {
                val body: ByteArray = it.readAllBytes()
                response.addHeader(
                    "Content-Disposition",
                    "attachment;filename=${URLEncoder.encode(templateName, StandardCharsets.UTF_8)}"
                )
                response.setContentLength(body.size)
                //获取Servlet的输出流ServletOutputStream
                response.outputStream.use { sos -> sos.write(body) }
            }
        } catch (e: IOException) {
            logger.error("下载模板出错$templateName", e)
            throw DownloadFileException("下载模板出错$templateName", e)
        }
    }

    @Commit
    override fun remove(entity: SysFileEntity) {
        try {
            val path: Path = Path.of(localFileUploadConfig.filePath + entity.dir)
            if (PathUtil.exists(path, false)) {
                // 如果存在文件则删除
                Files.delete(path)
            }
            sysFileDao.delete(entity)
        } catch (e: IOException) {
            logger.error(java.lang.String.format("删除文件%s(%s)发生异常", entity.fileName, entity.id), e)
        }
    }

    @Commit
    override fun remove(id: Long) {
        sysFileDao.findByIdOrNull(id)?.let {
            remove(it)
        }
    }

    @Commit
    override fun removeAll(entities: List<SysFileEntity>) {
        var entity: SysFileEntity? = null
        try {
            for (item in entities) {
                entity = item
                val path: Path = Path.of(localFileUploadConfig.filePath + entity.dir)
                if (PathUtil.exists(path, false)) {
                    // 如果存在文件则删除
                    Files.delete(path)
                }
            }
            sysFileDao.deleteAll(entities)
        } catch (e: IOException) {
            Objects.requireNonNull<SysFileEntity?>(entity)
            logger.error(java.lang.String.format("删除文件%s(%s)发生异常", entity?.fileName, entity?.id), e)
            throw DeleteFileException(java.lang.String.format("删除文件%s发生异常", entity?.fileName), e)
        }
    }

    override fun findById(id: Long): SysFileDto {
        return SysFileDto(sysFileDao.findById(id))
    }

    override fun findAllById(ids: List<Long>): List<SysFileDto> {
        return sysFileDao.findAllById(ids).map { SysFileDto(it) }
    }

    fun createKey(id: Long, type: FileType, suffix: String): String {
        return "${type.folder}${File.separator}${
            LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"))
        }${File.separator}${id}.${suffix}"
    }

    companion object {
        /**
         * 默认文件名后缀
         */
        private const val DEFAULT_FILE_SUFFIX = "jpg"
    }
}
